I wrote an article on the famous Spring's RestTemplate some months ago (http://vincentdevillers.blogspot.fr/2013/02/configure-best-spring-resttemplate.html). Since the last version of Spring 4, currently 4.0.0.M3, or the BUILD-SNAPSHOT for the most courageous, you can use a custom RestTemplate in an async way: the AsyncRestTemplate API.
Why should I use that?
The purpose of this API is to allow Java applications to easily execute HTTP requests and asynchronously process the HTTP responses. In non async mode, the code will block until the response is fully received. In async mode, the code will continue and a listener will warn you about the availability of the response (entire or parts of the response).
As the RestTemple uses a ClientHttpRequestFactory for creating HTTP connections, the AsyncRestTemple uses an... AsyncClientHttpRequestFactory. Actually, only the httpcomponents implementation (HttpComponentsAsyncClientHttpRequestFactory) and a basic implementation using the jdk classes (SimpleClientHttpRequestFactory) are ready to use, but we can bet than many other will come, supporting frameworks like Netty (http://netty.io/), Grizzly (https://grizzly.java.net/) or kryonet (http://code.google.com/p/kryonet/).
NB: SimpleClientHttpRequestFactory implements now both ClientHttpRequestFactory and AsyncClientHttpRequestFactory. The trick is, when using the async way, the SimpleClientHttpRequestFactory relies on a TaskExecutor.
First, you need this:html
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-web</artifactId> <version>4.0.0.M3</version> </dependency> <!-- Apache Http Client --> <dependency> <groupId>org.apache.httpcomponents</groupId> <artifactId>httpclient</artifactId> <version>4.3</version> </dependency> <dependency> <groupId>org.apache.httpcomponents</groupId> <artifactId>httpasyncclient</artifactId> <version>4.0-beta4</version> </dependency>
import java.util.List; import javax.inject.Inject; import org.apache.http.HttpHost; import org.apache.http.client.config.RequestConfig; import org.apache.http.conn.routing.HttpRoute; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClientBuilder; import org.apache.http.impl.conn.PoolingHttpClientConnectionManager; import org.apache.http.impl.nio.client.CloseableHttpAsyncClient; import org.apache.http.impl.nio.client.HttpAsyncClientBuilder; import org.apache.http.impl.nio.conn.PoolingNHttpClientConnectionManager; import org.apache.http.impl.nio.reactor.DefaultConnectingIOReactor; import org.apache.http.impl.nio.reactor.IOReactorConfig; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.http.client.AsyncClientHttpRequestFactory; import org.springframework.http.client.ClientHttpRequestFactory; import org.springframework.http.client.HttpComponentsAsyncClientHttpRequestFactory; import org.springframework.http.client.HttpComponentsClientHttpRequestFactory; import org.springframework.http.converter.HttpMessageConverter; import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; import org.springframework.web.client.AsyncRestTemplate; import org.springframework.web.client.RestTemplate; import com.fasterxml.jackson.databind.ObjectMapper; import com.google.common.base.Throwables; @Configuration public class HttpConfig { private static final int DEFAULT_MAX_TOTAL_CONNECTIONS = 100; private static final int DEFAULT_MAX_CONNECTIONS_PER_ROUTE = 5; private static final int DEFAULT_READ_TIMEOUT_MILLISECONDS = (60 * 1000); @Inject private ObjectMapper objectMapper; // ################################################### SYNC @Bean public ClientHttpRequestFactory httpRequestFactory() { return new HttpComponentsClientHttpRequestFactory(httpClient()); } @Bean public RestTemplate restTemplate() { RestTemplate restTemplate = new RestTemplate(httpRequestFactory()); List<HttpMessageConverter<?>> converters = restTemplate .getMessageConverters(); for (HttpMessageConverter<?> converter : converters) { if (converter instanceof MappingJackson2HttpMessageConverter) { MappingJackson2HttpMessageConverter jsonConverter = (MappingJackson2HttpMessageConverter) converter; jsonConverter.setObjectMapper(objectMapper); } } return restTemplate; } @Bean public CloseableHttpClient httpClient() { PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(); connectionManager.setMaxTotal(DEFAULT_MAX_TOTAL_CONNECTIONS); connectionManager .setDefaultMaxPerRoute(DEFAULT_MAX_CONNECTIONS_PER_ROUTE); connectionManager.setMaxPerRoute(new HttpRoute(new HttpHost( "facebook.com")), 20); connectionManager.setMaxPerRoute(new HttpRoute(new HttpHost( "twitter.com")), 20); connectionManager.setMaxPerRoute(new HttpRoute(new HttpHost( "linkedin.com")), 20); connectionManager.setMaxPerRoute(new HttpRoute(new HttpHost( "viadeo.com")), 20); RequestConfig config = RequestConfig.custom() .setConnectTimeout(DEFAULT_READ_TIMEOUT_MILLISECONDS).build(); CloseableHttpClient defaultHttpClient = HttpClientBuilder.create() .setConnectionManager(connectionManager) .setDefaultRequestConfig(config).build(); return defaultHttpClient; } // ################################################### ASYNC @Bean public AsyncClientHttpRequestFactory asyncHttpRequestFactory() { return new HttpComponentsAsyncClientHttpRequestFactory( asyncHttpClient()); } @Bean public AsyncRestTemplate asyncRestTemplate() { AsyncRestTemplate restTemplate = new AsyncRestTemplate( asyncHttpRequestFactory(), restTemplate()); return restTemplate; } @Bean public CloseableHttpAsyncClient asyncHttpClient() { try { PoolingNHttpClientConnectionManager connectionManager = new PoolingNHttpClientConnectionManager( new DefaultConnectingIOReactor(IOReactorConfig.DEFAULT)); connectionManager.setMaxTotal(DEFAULT_MAX_TOTAL_CONNECTIONS); connectionManager .setDefaultMaxPerRoute(DEFAULT_MAX_CONNECTIONS_PER_ROUTE); connectionManager.setMaxPerRoute(new HttpRoute(new HttpHost( "facebook.com")), 20); connectionManager.setMaxPerRoute(new HttpRoute(new HttpHost( "twitter.com")), 20); connectionManager.setMaxPerRoute(new HttpRoute(new HttpHost( "linkedin.com")), 20); connectionManager.setMaxPerRoute(new HttpRoute(new HttpHost( "viadeo.com")), 20); RequestConfig config = RequestConfig.custom() .setConnectTimeout(DEFAULT_READ_TIMEOUT_MILLISECONDS) .build(); CloseableHttpAsyncClient httpclient = HttpAsyncClientBuilder .create().setConnectionManager(connectionManager) .setDefaultRequestConfig(config).build(); return httpclient; } catch (Exception e) { throw Throwables.propagate(e); } } }
測試java
As you can see, the AsyncRestTemplate uses an underlying RestTemplate. This template is used for all the configuration (http message converters, errors handler...).react
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import java.util.Map; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; import javax.inject.Inject; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import org.springframework.test.context.support.AnnotationConfigContextLoader; import org.springframework.web.client.AsyncRestTemplate; import org.springframework.web.client.RestTemplate; import com.fasterxml.jackson.databind.ObjectMapper; import com.google.common.util.concurrent.FutureCallback; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.JdkFutureAdapters; import com.leguide.Feed.BindConfig; @ContextConfiguration(loader = AnnotationConfigContextLoader.class, classes = { HttpConfig.class, BindConfig.class }) @RunWith(SpringJUnit4ClassRunner.class) public class Feed { @Configuration static class BindConfig { @Bean public ObjectMapper objectMapper() { return new ObjectMapper(); } } @Inject private RestTemplate restTemplate; @Inject private AsyncRestTemplate asyncRestTemplate; @Test public void sync() { // Arrange String url = "https://api.github.com/users/treydone"; // Actions ResponseEntity<Map> entity = restTemplate.getForEntity(url, Map.class); // Asserts assertTrue(entity.getStatusCode().equals(HttpStatus.OK)); assertEquals("Treydone", entity.getBody().get("login")); } @Test public void async_withFuture() throws InterruptedException, ExecutionException { // Arrange String url = "https://api.github.com/users/treydone"; // Actions Future<ResponseEntity<Map>> future = asyncRestTemplate.getForEntity( url, Map.class); while (!future.isDone()) { TimeUnit.MILLISECONDS.sleep(100); } ResponseEntity<Map> entity = future.get(); // Asserts assertTrue(entity.getStatusCode().equals(HttpStatus.OK)); assertEquals("Treydone", entity.getBody().get("login")); } @Test public void async_withCallback() throws InterruptedException { // Arrange String url = "https://api.github.com/users/treydone"; ExecutorService executorService = Executors.newFixedThreadPool(1); // Actions Futures.addCallback( JdkFutureAdapters.listenInPoolThread( asyncRestTemplate.getForEntity(url, Map.class), executorService), new FutureCallback<ResponseEntity<Map>>() { @Override public void onSuccess(ResponseEntity<Map> entity) { // Asserts assertTrue(entity.getStatusCode().equals(HttpStatus.OK)); assertEquals("Treydone", entity.getBody().get("login")); } @Override public void onFailure(Throwable t) { } }, executorService); TimeUnit.SECONDS.sleep(3); } }
The new async API for RestTemplate is really easy to use and the transition between sync to async seems to be effortless. Spring continue to offers a good abstraction over http frameworks and the REST capacities powered by, among other, the underlying HttpMessageConverters, are also available in the async API, priceless!
But the API is still young, and maybe not as production-ready, as the async-http-client from Ning (https://github.com/AsyncHttpClient/async-http-client), which supports Grizzly, Netty and Apache as underlying providers, and is well documented (AsyncRestTemplate is not yet documented on spring.io: http://docs.spring.io/spring/docs/4.0.x/spring-framework-reference/html/remoting.html#rest-resttemplate). Moreover, one big missing thing is a provided callback in the methods: all methods return a Future, but if you want to play with callbacks (Runnable or Callable) or ListenableFuture, you have to cheat with Guava, when others frameworks can do it natively:git
<dependency> <groupId>com.ning</groupId> <artifactId>async-http-client</artifactId> <version>1.7.20</version> </dependency>
@Test public void ning() throws InterruptedException, IOException { // Arrange String url = "https://api.github.com/users/treydone"; AsyncHttpClient client = new AsyncHttpClient(); // Actions client.prepareGet(url).execute(new AsyncCompletionHandlerBase() { @Override public Response onCompleted(Response response) throws Exception { // Asserts assertTrue(response.getStatusCode() == HttpStatus.OK.value()); assertEquals( "Treydone", objectMapper.readValue(response.getResponseBody(), Map.class).get("login")); return super.onCompleted(response); } }); client.close(); }