J'utilise le client Apache http dans Spring MVC 3.2.2 pour envoyer 5 requêtes Get de manière synchrone, comme illustré.
Comment puis-je envoyer tous ces éléments de manière asynchrone (en parallèle) et attendre le retour des demandes afin de renvoyer une chaîne de charge utile analysée à partir de toutes les demandes GET?
public String myMVCControllerGETdataMethod()
{
// Send 1st request
HttpClient httpclient = new DefaultHttpClient();
HttpGet httpget = new HttpGet("http://api/data?type=1");
ResponseHandler<String> responseHandler = new BasicResponseHandler();
String responseBody = httpclient.execute(httpget, responseHandler);
// Send 2st request
HttpClient httpclient2 = new DefaultHttpClient();
HttpGet httpget2 = new HttpGet("http://api/data?type=2");
ResponseHandler2<String> responseHandler2 = new BasicResponseHandler();
String responseBody2 = httpclient.execute(httpget, responseHandler2);
// o o o more gets here
// Perform some work here...and wait for all requests to return
// Parse info out of multiple requests and return
String results = doWorkwithMultipleDataReturned();
model.addAttribute(results, results);
return "index";
}
De manière générale, vous devez encapsuler vos unités de travail dans un Runnable
ou Java.util.concurrent.Callable
et les exécuter via Java.util.concurrent.Executor
(ou org.springframework.core.task.TaskExecutor
). Cela permet à chaque unité de travail d'être exécutée séparément, généralement de manière asynchrone (selon l'implémentation de Executor
).
Donc, pour votre problème spécifique, vous pouvez faire quelque chose comme ceci:
import Java.util.ArrayList;
import Java.util.Iterator;
import Java.util.List;
import Java.util.concurrent.Callable;
import Java.util.concurrent.Executor;
import Java.util.concurrent.FutureTask;
import org.Apache.http.client.methods.HttpGet;
import org.Apache.http.impl.client.BasicResponseHandler;
import org.Apache.http.impl.client.DefaultHttpClient;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
public class MyController {
//inject this
private Executor executor;
@RequestMapping("/your/path/here")
public String myMVCControllerGETdataMethod(Model model) {
//define all async requests and give them to injected Executor
List<GetRequestTask> tasks = new ArrayList<GetRequestTask>();
tasks.add(new GetRequestTask("http://api/data?type=1", this.executor));
tasks.add(new GetRequestTask("http://api/data?type=2", this.executor));
//...
//do other work here
//...
//now wait for all async tasks to complete
while(!tasks.isEmpty()) {
for(Iterator<GetRequestTask> it = tasks.iterator(); it.hasNext();) {
GetRequestTask task = it.next();
if(task.isDone()) {
String request = task.getRequest();
String response = task.getResponse();
//PUT YOUR CODE HERE
//possibly aggregate request and response in Map<String,String>
//or do something else with request and response
it.remove();
}
}
//avoid tight loop in "main" thread
if(!tasks.isEmpty()) Thread.sleep(100);
}
//now you have all responses for all async requests
//the following from your original code
//note: you should probably pass the responses from above
//to this next method (to keep your controller stateless)
String results = doWorkwithMultipleDataReturned();
model.addAttribute(results, results);
return "index";
}
//abstraction to wrap Callable and Future
class GetRequestTask {
private GetRequestWork work;
private FutureTask<String> task;
public GetRequestTask(String url, Executor executor) {
this.work = new GetRequestWork(url);
this.task = new FutureTask<String>(work);
executor.execute(this.task);
}
public String getRequest() {
return this.work.getUrl();
}
public boolean isDone() {
return this.task.isDone();
}
public String getResponse() {
try {
return this.task.get();
} catch(Exception e) {
throw new RuntimeException(e);
}
}
}
//Callable representing actual HTTP GET request
class GetRequestWork implements Callable<String> {
private final String url;
public GetRequestWork(String url) {
this.url = url;
}
public String getUrl() {
return this.url;
}
public String call() throws Exception {
return new DefaultHttpClient().execute(new HttpGet(getUrl()), new BasicResponseHandler());
}
}
}
Notez que ce code n'a pas été testé.
Pour votre implémentation Executor
, consultez TaskExecutor de Spring et tâche: espace de noms de l'exécuteur . Vous voulez probablement un pool de threads réutilisable pour ce cas d'utilisation (au lieu de créer un nouveau thread à chaque fois).
Vous devez utiliser AsyncHttpClient. Vous pouvez faire n'importe quel nombre de demandes et il vous rappellera lorsqu'il recevra une réponse. Vous pouvez configurer le nombre de connexions qu'il peut créer. Tous les threads sont gérés par la bibliothèque, il est donc beaucoup plus facile que de gérer les threads vous-même.
jetez un oeil à l'exemple ici: https://github.com/AsyncHttpClient/async-http-client
Pour l'exécution parallèle de plusieurs requêtes avec une seule instance HttpClient.
configurer PoolingHttpClientConnectionManager pour une exécution parallèle.
HttpClientBuilder builder = HttpClientBuilder.create();
PlainConnectionSocketFactory plainConnectionSocketFactory = new PlainConnectionSocketFactory();
Registry<ConnectionSocketFactory> registry = RegistryBuilder.<ConnectionSocketFactory>create()
.register("http", plainConnectionSocketFactory).build();
PoolingHttpClientConnectionManager ccm = new PoolingHttpClientConnectionManager(registry);
ccm.setMaxTotal(BaseConstant.CONNECTION_POOL_SIZE); // For Example : CONNECTION_POOL_SIZE = 10 for 10 thread parallel execution
ccm.setDefaultMaxPerRoute(BaseConstant.CONNECTION_POOL_SIZE);
builder.setConnectionManager((HttpClientConnectionManager) ccm);
HttpClient objHttpClient = builder.build();
Déplacez votre code de demande vers une méthode distincte:
private String executeGet(String url){
HttpClient httpclient = new DefaultHttpClient();
HttpGet httpget = new HttpGet(url);
ResponseHandler<String> responseHandler = new BasicResponseHandler();
return httpclient.execute(httpget, responseHandler);
}
Et soumettez-les à ExecutorService
:
ExecutorService executorService = Executors.newCachedThreadPool();
Future<String> firstCallFuture = executorService.submit(() -> executeGet(url1));
Future<String> secondCallFuture = executorService.submit(() -> executeGet(url2));
String firstResponse = firstCallFuture.get();
String secondResponse = secondCallFuture.get();
executorService.shutdown();
Ou
Future<String> firstCallFuture = CompletableFuture.supplyAsync(() -> executeGet(url1));
Future<String> secondCallFuture = CompletableFuture.supplyAsync(() -> executeGet(url2));
String firstResponse = firstCallFuture.get();
String secondResponse = secondCallFuture.get();
Ou utilisez RestTemplate comme décrit dans Comment utiliser Spring WebClient pour effectuer plusieurs appels simultanément?