控制器甚至在使用异步和可完成的未来之后等待

问题描述 投票:0回答:1

我正在开发一个 Spring Boot 应用程序,我想在其中使用 CompletableFuture 和 @Async 注释使我的控制器异步。为了测试实现,我特意在服务方法中添加了一个 Thread.sleep() 调用。然而,即使使用这些异步方法,控制器在执行之前仍然需要等待一段时间。我已经验证我的控制器和服务在不同的线程上运行,正如它们不同的线程 ID 所证明的那样。我不确定为什么会这样,如果有任何关于如何解决此问题的见解或建议,我将不胜感激。

代码如下:

//controller
  @PostMapping
    public CompletableFuture<User> createUser(@RequestBody User user) {
        System.out.println(Thread.currentThread().getId()+"-----------------------------------"+System.currentTimeMillis()+"----------"+"inside controller");
        return userService.createUser(user);
    }

// 服务

   @Override
    @Async("asyncExecutor")
    public CompletableFuture<User> createUser(User user) {
        System.out.println(Thread.currentThread().getId()+"-----------------------------------"+System.currentTimeMillis()+"-------------inside services");
        return CompletableFuture.supplyAsync(() ->{
            try {
                Thread.sleep(1000000000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            System.out.println(Thread.currentThread().getId());
           return userRepository.save(user);
        });
    }

提供一些代码示例或有关您的实现的具体细节,以便更清楚地了解问题。

//输出

21-----------------------------------1683404458540----------inside controller
34-----------------------------------1683404458543-------------inside services
2023-05-07 01:51:28.812  WARN 36119 --- [nio-8080-exec-2] .w.s.m.s.DefaultHandlerExceptionResolver : Resolved [org.springframework.web.context.request.async.AsyncRequestTimeoutException]

java spring-boot asynchronous completable-future
1个回答
0
投票

Thread.sleep(1000000000);
对于(构建)测试来说似乎太多了......这指的是〜12(太阳)天!

这有效:

  • 启动器

  • “应用程序”:

    package com.example.demo;
    
    import java.util.UUID;
    import java.util.concurrent.CompletableFuture;
    import java.util.concurrent.TimeUnit;
    
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.scheduling.annotation.Async;
    import org.springframework.scheduling.annotation.EnableAsync;
    import org.springframework.stereotype.Controller;
    import org.springframework.stereotype.Service;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.RequestParam;
    import org.springframework.web.bind.annotation.ResponseBody;
    
    import lombok.extern.slf4j.Slf4j;
    
    @EnableAsync
    @SpringBootApplication
    public class DemoApplication {
    
      public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
      }
    
      @Slf4j
      @Service
      static class MyAsyService {
        @Value("${simulation.sleep.seconds:10}")
        private Long simSeconds;
    
        @Async
        CompletableFuture<String> doSomething(String req) throws InterruptedException {
          log.info("in service...");
          TimeUnit.SECONDS.sleep(simSeconds);
          log.info("...leaving service");
          return CompletableFuture.completedFuture(req + "_" + UUID.randomUUID());
        }
      }
    
      @Slf4j
      @Controller
      static class MyController {
        @Autowired
        MyAsyService service;
    
        @GetMapping("/hello")
        @ResponseBody
        CompletableFuture<String> hello(@RequestParam(required = false, defaultValue = "anonymous") String req)
            throws InterruptedException {
          log.info("in controller...");
          CompletableFuture<String> result = service.doSomething(req);
          log.info("...controller done!");
          return result;
        }
      }
    }
    
  • 在浏览器中:

  • 在服务器日志中:

    ... o.s.web.servlet.DispatcherServlet    : Completed initialization in 1 ms
    ... DemoApplication$MyController : in controller.... <= req#1
    ... DemoApplication$MyController : ....controller done!
    ... DemoApplication$MyAsyService : in service....
    ... DemoApplication$MyAsyService : ....leaving service
    ... DemoApplication$MyController : in controller.... <= req#2
    ... DemoApplication$MyController : ....controller done!
    ... DemoApplication$MyAsyService : in service....
    ... DemoApplication$MyAsyService : ....leaving service
    ... DemoApplication$MyController : in controller.... <= req#3
    ... DemoApplication$MyController : ....controller done!
    ... DemoApplication$MyAsyService : in service....
    ... DemoApplication$MyAsyService : ....leaving service
    
  • (自动,模拟 mvc)测试(最难的部分;):

    package com.example.demo;
    
    import static org.hamcrest.core.StringStartsWith.startsWith;
    import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
    import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
    import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.request;
    import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
    
    import java.util.UUID;
    
    import org.junit.jupiter.api.Test;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
    import org.springframework.mock.web.MockHttpServletRequest;
    import org.springframework.test.web.servlet.MockMvc;
    import org.springframework.test.web.servlet.MvcResult;
    import org.springframework.test.web.servlet.RequestBuilder;
    
    @WebMvcTest
    class DemoApplicationTests {
    
      @Autowired
      MockMvc mockMvc;
      @Value("${simulation.sleep.seconds}") // -> src/test/resources/application.properties
      Long simSeconds;
    
      @Test
      void testController() throws Exception {
        String uid = UUID.randomUUID().toString();
        MvcResult mvcResult = mockMvc.perform(get("/hello?req={v}", uid)) //1.
            .andExpect(request().asyncStarted()) // 2.
            .andReturn(); // 3.
    
        mockMvc.perform(asyncDispatch(mvcResult/*, (simSeconds + 1) * 1000*/)) // 4!*
            .andExpectAll( // ...
                status().isOk(),
                content().string(startsWith(uid)));
      }
      // 4*: when we take/simulate more than ~10 seconds, we have to & can re-implement/enrich:
      static RequestBuilder asyncDispatch(MvcResult mvcResult, long timeToWait) {
    
          // copied/adopted from MockMvcRequestBuilders#asyncDispatch
          mvcResult.getAsyncResult(timeToWait);
    
          return servletContext -> {
              MockHttpServletRequest request = mvcResult.getRequest();
              request.setDispatcherType(DispatcherType.ASYNC);
              request.setAsyncStarted(false);
              return request;
          };
      }
    }
    
© www.soinside.com 2019 - 2024. All rights reserved.