springboot异步方法调用

通常情况下,假定需要调用3个Service:Service A,Service B,Service C。假定A、B、C之间没有关联关系,如果每个方法需要耗时3秒,那么总共需要耗费9秒。
这显然不是我们希望的。

我们希望Service A,B,C同时调用,然后等待A、B、C全部结束,最后汇总执行结果。在SpringBoot中,实现这一点是很简单的。

  1. 在@SpringBootApplication注解下的应用程序类上使用@EnableAsync注解;
  2. 启用后,可以在返回CompletableFuture<>的服务中使用@Async注解。 因为你有@EnableAsync,@Async方法将在后台线程池中运行;
  3. 等待所有调用执行完毕;
  4. 汇总结果。

下面我们用一个小例子来说明。

我们将构建一个查找服务,用于查询GitHub用户信息,并通过GitHub的API检索数据。
我们可以通过Spring Initializr来快速创建一个spring boot应用。这里不具体赘述。

1.maven依赖
这里只需要即可。

1
2
3
4
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

2.创建一个User用于接收数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;

@JsonIgnoreProperties(ignoreUnknown=true)
public class User {

private String name;
private String blog;

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public String getBlog() {
return blog;
}

public void setBlog(String blog) {
this.blog = blog;
}

@Override
public String toString() {
return "User [name=" + name + ", blog=" + blog + "]";
}

}

3.创建一个GitHubService来提供查找服务

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@Service
public class GitHubLookupService {

private static final Logger logger = LoggerFactory.getLogger(GitHubLookupService.class);

private final RestTemplate restTemplate;

public GitHubLookupService(RestTemplateBuilder restTemplateBuilder) {
this.restTemplate = restTemplateBuilder.build();
}

@Async
public CompletableFuture<User> findUser(String user) throws InterruptedException {
logger.info("Looking up " + user);
String url = String.format("https://api.github.com/users/%s", user);
User results = restTemplate.getForObject(url, User.class);
// Artificial delay of 1s for demonstration purposes
Thread.sleep(1000L);
return CompletableFuture.completedFuture(results);
}

}

4.springboot启动类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;

import java.util.concurrent.Executor;

@SpringBootApplication
@EnableAsync
public class AsyncMethodApplication {

public static void main(String[] args) {
// close the application context to shut down the custom ExecutorService
SpringApplication.run(AsyncMethodApplication.class, args).close();
}

@Bean
public Executor taskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(2);
executor.setMaxPoolSize(2);
executor.setQueueCapacity(500);
executor.setThreadNamePrefix("GithubLookup-");
executor.initialize();
return executor;
}


}

此类还通过定义一个新bean来自定义执行程序。 在这里,该方法被命名为taskExecutor,因为这是Spring搜索的特定方法名称。 在我们的例子中,我们希望将并发线程的数量限制为两个,并将队列的大小限制为500。
还有更多的事情,你可以调整。 如果你不定义一个Executor bean,Spring会创建一个Simplieasynctaskexecutor并使用它。

最后,我们定义了一个测试的Controller:

@Slf4j
@RestController
@RequestMapping("/asyncMethod")
public class AsyncMethodTestController {
    @Autowired
    private GitHubLookupService gitHubLookupService;

    @GetMapping("/test")
    public Object test() throws ExecutionException, InterruptedException {
        // Start the clock
        long start = System.currentTimeMillis();

        // Kick of multiple, asynchronous lookups
        CompletableFuture<User> page1 = gitHubLookupService.findUser("PivotalSoftware");
        CompletableFuture<User> page2 = gitHubLookupService.findUser("CloudFoundry");
        CompletableFuture<User> page3 = gitHubLookupService.findUser("Spring-Projects");

        // Wait until they are all done
        CompletableFuture.allOf(page1,page2,page3).join();

        // Print results, including elapsed time
        log.info("Elapsed time: " + (System.currentTimeMillis() - start));
        List<User> result = new ArrayList<>(3);
        result.add(page1.get());
        result.add(page2.get());
        result.add(page3.get());
        return result;
    }
}

启动应用,访问/asyncMethod/test,我们可以得到下面的结果

控制台打印如下:

我们在查找方法中故意休眠1秒,调用3次。总耗时不到3s,这还不包括本身访问github api的耗时。

如果能够很好的利用异步执行,可以使你的服务快速响应。

参考:https://spring.io/guides/gs/async-method/https://spring.io/guides/gs/async-method/

Author: Donny
Link: https://tommy88.top/2019/10/22/springboot异步方法调用/
Copyright Notice: All articles in this blog are licensed under CC BY-NC-SA 4.0 unless stating additionally.
微信打赏