如何与Spring一起制作一个异步REST?

2022-09-01 14:40:22

我正在尝试使用Spring Boot制作一个小的REST。我以前从未使用过Spring,很久以前就使用过Java(Java 7)!

在过去的2年里,我只使用Python和C#(但就像我说的,我已经使用了Java)。

所以,现在,我正在尝试使用异步方法制作REST,并检查了几个例子,但是,我仍然不太理解这样做的“正确方法”。

查看以下文档:http://carlmartensen.com/completablefuture-deferredresult-async,Java 8具有我可以与Spring一起使用的功能,因此,我编写了以下代码:CompletableFuture

服务

@Service
public class UserService {
  private UserRepository userRepository;

  // dependency injection
  // don't need Autowire here
  // https://docs.spring.io/spring-boot/docs/current/reference/html/using-boot-spring-beans-and-dependency-injection.html
  public UserService(UserRepository userRepository) {
    this.userRepository = userRepository;
  }

  @Async
  public CompletableFuture<User> findByEmail(String email) throws InterrupedException {
    User user = userRepository.findByEmail(email);
    return CompletableFuture.completedFuture(user);
  }
}

存储库

public interface UserRepository extends MongoRepository<User, String> {
  @Async
  findByEmail(String email);
}

休息控制器

@RestController
public class TestController {

  private UserService userService;

  public TestController(UserService userService) {
    this.userService = userService;
  }

  @RequestMapping(value = "test")
  public @ResponseBody CompletableFuture<User> test(@RequestParam(value = "email", required = true) String email) throws InterruptedException {
    return userService.findByEmail(email).thenApplyAsync(user -> {
      return user;
    })
  }  
}

此代码为我提供了预期的输出。然后,查看另一个文档(抱歉,我丢失了链接),我看到Spring接受以下代码(这也给了我预期的输出):

  @RequestMapping(value = "test")
  public @ResponseBody CompletableFuture<User> test(@RequestParam(value = "email", required = true) String email) throws InterruptedException {
    return userService.findByEmail(email);
  }  
}

这两种方法之间有区别吗?

然后,查看以下指南:https://spring.io/guides/gs/async-method/,课堂上有一个注释。如果我包含注释并创建一个像上一个链接中的代码一样的Bean,我的应用程序在端点上不会返回任何内容(只有200 OK响应,但带有空白正文)。@EnableAsyncSpringBootApplication@EnableAsyncasyncExecutor/test

那么,我的休息是异步的,没有注释?为什么当我使用时,响应正文是空白的?@EnableAsync@EnableAsync


答案 1

响应正文为空,因为注释是在 UserRepository 类的 findEmail 方法中使用的,这意味着没有数据返回到下面的句子,因为 findByEmail 方法在其他不同的线程上运行,并将返回 null 而不是 List 对象。@AsyncUser user = userRepository.findByEmail(email);

当您声明注释时,将启用该批注,这是仅在您使用时发生的原因,因为它激活了 findEmail 方法@Async以在其他线程上运行它。@Async@EnableAsync@EnableAsync

该方法将返回从类创建的对象。return userService.findByEmail(email);CompletableFutureUserService

与第二个方法调用的不同之处在于,该方法将创建一个来自上一个方法的全新方法,并且只会返回来自第一个方法的用户对象。thenApplyAsyncCompletableFutureuserService.findByEmail(email)CompletableFuture

 return userService.findByEmail(email).thenApplyAsync(user -> {
      return user;
    })

如果要获得预期的结果,只需从findByEmail方法中删除注释,最后添加注释@Async@EnableAsync

如果您需要阐明如何使用异步方法的想法,假设您必须调用三个方法,每个方法需要2秒钟才能完成,在正常情况下,您将调用它们formress1,然后methor2和最后的方法3在这种情况下,您的整个请求将花费6秒。当您激活异步方法时,您可以调用其中的三个,只需等待2秒而不是6秒。

将此长方法添加到用户服务:

@Async
public  CompletableFuture<Boolean> veryLongMethod()  {

    try {
        Thread.sleep(2000L);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }

    return CompletableFuture.completedFuture(true);
}

并从控制器调用它三次,就像这样

  @RequestMapping(value = "test")
  public @ResponseBody CompletableFuture<User> test(@RequestParam(value = "email", required = true) String email) throws InterruptedException {
        CompletableFuture<Boolean> boolean1= siteService.veryLongMethod();
        CompletableFuture<Boolean> boolean2= siteService.veryLongMethod();
        CompletableFuture<Boolean> boolean3= siteService.veryLongMethod();

        CompletableFuture.allOf(boolean1,boolean2,boolean3).join();
    return userService.findByEmail(email);
  }  

最后测量响应所需的时间,如果它花费的时间超过6秒,那么你没有运行异步方法,如果它只需要2秒,那么你成功了。

另请参阅以下文档:@Async AnnotationSpring 异步方法CompletableFuture 类

希望它有帮助。


答案 2

异步子线程开始执行的时间非常晚(大约 20 到 30 秒延迟)。

我在我的主要SpringBoot应用程序类中使用ThreadPoolTaskExecutor()。如果您将性能视为一个因素,您也可以尝试相同的方法。


推荐