Java和/或Spring引导中的并发异步HTTP请求
我需要(用Java)解决的问题是:Java和/或Spring引导中的并发异步HTTP请求,java,spring-boot,Java,Spring Boot,我需要(用Java)解决的问题是: 从API获取游戏的数组 在游戏上迭代N次: (异步/并发) 为每个游戏发出一个单独的HTTP请求,以获取其 细节 将其详细信息存储在gamesWithDetails数组中 完成了。我有我的数组gamesWithDetails 我无法通过一个请求获取所有游戏的详细信息,每次游戏我都必须点击API端点。因此,我希望彼此异步执行这些请求 这是JavaScript中的一个工作示例,如果有用的话。然而,我想让它的工作为春季开机 axios.get(`https:/
游戏的数组李>
在游戏上迭代
N次:
- (异步/并发)
- 为每个游戏发出一个单独的HTTP请求,以获取其
细节
- 将其详细信息存储在
gamesWithDetails
数组中
gamesWithDetails
axios.get(`https://la2.api.riotgames.com/lol/match/v4/matchlists/by-account/${data.accountId}`, {
headers: { "X-Riot-Token": "asdasdasdasdadasdasdasd"}
})
.then(resp => {
const promises = [];
for ( match of resp.data.matches ) {
promises.push(
axios.get(`https://la2.api.riotgames.com/lol/match/v4/matches/${match.gameId}`, {
headers: { "X-Riot-Token": "asdasdasdasdasdasdasdasd"}
})
)
}
Promise.all(promises)
.then(matchesDetails => {
matchesDetails.forEach(({ data }) => console.log(data.gameId));
});
})
基本上你会想做这样的事情:
package com.example.demo;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.client.RestTemplate;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import java.util.stream.Collectors;
public class GamesProcessor {
private static final String GAME_URI_BASE = "https://la2.api.riotgames.com/lol/match/v4/matches/";
private static final String ACCOUNT_URI_BASE = "https://la2.api.riotgames.com/lol/match/v4/matchlists/by-account/";
private Executor executor = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors() - 1);
@Autowired
private RestTemplate restTemplate;
public void processGames(String accountId) throws JsonProcessingException, ExecutionException, InterruptedException {
String responseAsString = restTemplate.getForObject(ACCOUNT_URI_BASE + accountId, String.class);
ObjectMapper objectMapper = new ObjectMapper();
if (responseAsString != null) {
Map<String, Object> response = objectMapper.readValue(responseAsString, new TypeReference<Map<String, Object>>() {
});
List<Map<String, Object>> matches = (List<Map<String, Object>>) ((Map<String, Object>) response.get("data")).get("matches");
List<CompletableFuture<Void>> futures = matches.stream()
.map(m -> (String) m.get("gameId"))
.map(gameId -> CompletableFuture.supplyAsync(() -> restTemplate.getForObject(GAME_URI_BASE + gameId, String.class), executor)
.thenAccept(r -> {
System.out.println(r); //do whatever you wish with the response here
}))
.collect(Collectors.toList());
// now we execute all requests asynchronously
CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).get();
}
}
}
这将执行第一个REST请求,并将其作为字符串(JSON响应)获取。您将希望使用Bean对象来正确地映射它。然后使用Jackson提供的ObjectMapper对其进行处理,并将其转换为一个映射,以便您可以导航JSON并获得匹配项
List<CompletableFuture<Void>> futures = matches.stream()
.map(m -> (String) m.get("gameId"))
.map(gameId -> CompletableFuture.supplyAsync(() -> restTemplate.getForObject(GAME_URI_BASE + gameId, String.class), executor)
.thenAccept(r -> {
System.out.println(r); //do whatever you wish with the response here
}))
.collect(Collectors.toList());
这将针对每个matchId得到的每个响应执行,就像在您的示例中一样。这也应该被一个与输出相匹配的适当bean所代替,以便进行更清晰的处理
请注意,
List futures
仅“保存代码”,但在我们最终使用CompletableFuture.allOf(futures.toArray(new CompletableFuture[0]).get()组合所有内容之前,不会执行它编码>并执行阻塞get()
方法。这是一个非常有趣的问题,因为JavaScript实现了著名的方法,这意味着它的函数是异步的和非阻塞的。Spring BootrestTemplate
类将阻塞执行线程,直到响应返回,因此会浪费大量资源(每个请求模型一个线程)
@Slacky的回答在技术上是正确的,正如您所问的异步HTTP请求,但我想分享一个更好的选择,即异步和非阻塞,这意味着单个线程能够处理100甚至1000个请求及其响应(反应式编程)
在springboot中实现与JavaScript示例等效的方法是使用projectreactorWebClient
类,它是一个非阻塞、反应式客户端,用于执行HTTP请求
还值得一提的是,静态类型化的Java要求您声明类来表示数据,在本例中类似(为了简洁起见,使用Lombok):
以下代码遵循@Slacky的答案命名约定,以便于比较
public class GamesProcessor {
private static final String BASE_URL = "https://la2.api.riotgames.com";
private static final String GAME_URI = "/lol/match/v4/matches/%s";
private static final String ACCOUNT_URI = "/lol/match/v4/matchlists/by-account/%s";
public static List<MatchDetails> processGames(String accountId) {
final WebClient webClient = WebClient
.builder()
.baseUrl(BASE_URL)
.defaultHeader("X-Riot-Token", "asdasdasdasdadasdasdasd")
.build();
// Issues the first request to get list of matches
List<Match> matches = webClient
.get()
.uri(String.format(ACCOUNT_URI, accountId))
.accept(MediaType.APPLICATION_JSON)
.retrieve()
.bodyToMono(new ParameterizedTypeReference<List<Match>>() {})
.block(); // blocks to wait for response
// Processes the list of matches asynchronously and collect all responses in a list of matches details
return Flux.fromIterable(matches)
.flatMap(match -> webClient
.get()
.uri(String.format(GAME_URI, match.getGameId()))
.accept(MediaType.APPLICATION_JSON)
.retrieve()
.bodyToMono(MatchDetails.class))
.collectList()
.block(); // Blocks to wait for all responses
}
}
公共类游戏处理器{
私有静态最终字符串BASE_URL=”https://la2.api.riotgames.com";
私有静态最终字符串GAME_URI=“/lol/match/v4/matches/%s”;
私有静态最终字符串ACCOUNT_URI=“/lol/match/v4/matchlist/by ACCOUNT/%s”;
公共静态列表processGames(字符串accountId){
最终网络客户端网络客户端=网络客户端
.builder()
.baseUrl(基本URL)
.defaultHeader(“X-Riot-Token”、“asdasdasd”)
.build();
//发出第一个请求以获取匹配项列表
列表匹配项=webClient
.get()
.uri(String.format(ACCOUNT_uri,accountId))
.accept(MediaType.APPLICATION_JSON)
.retrieve()
.BodyToNo(新的参数化类型引用(){})
.block();//等待响应的块
//异步处理匹配列表并收集匹配详细信息列表中的所有响应
返回通量.fromIterable(匹配项)
.flatMap(匹配->网络客户端)
.get()
.uri(String.format(GAME_uri,match.getGameId()))
.accept(MediaType.APPLICATION_JSON)
.retrieve()
.BodyToNo(匹配详细信息.class))
.LIST()
.block();//等待所有响应的块
}
}
System.out.println(r);
@Data
class Match {
private String gameId;
// ...
}
@Data
class MatchDetails {
// ...
}
public class GamesProcessor {
private static final String BASE_URL = "https://la2.api.riotgames.com";
private static final String GAME_URI = "/lol/match/v4/matches/%s";
private static final String ACCOUNT_URI = "/lol/match/v4/matchlists/by-account/%s";
public static List<MatchDetails> processGames(String accountId) {
final WebClient webClient = WebClient
.builder()
.baseUrl(BASE_URL)
.defaultHeader("X-Riot-Token", "asdasdasdasdadasdasdasd")
.build();
// Issues the first request to get list of matches
List<Match> matches = webClient
.get()
.uri(String.format(ACCOUNT_URI, accountId))
.accept(MediaType.APPLICATION_JSON)
.retrieve()
.bodyToMono(new ParameterizedTypeReference<List<Match>>() {})
.block(); // blocks to wait for response
// Processes the list of matches asynchronously and collect all responses in a list of matches details
return Flux.fromIterable(matches)
.flatMap(match -> webClient
.get()
.uri(String.format(GAME_URI, match.getGameId()))
.accept(MediaType.APPLICATION_JSON)
.retrieve()
.bodyToMono(MatchDetails.class))
.collectList()
.block(); // Blocks to wait for all responses
}
}