如何使用Spring Boot向控制器实现异步REST请求?

时间:2015-03-06 21:46:33

标签: asynchronous spring-boot

我正在尝试使用SprintBoot实现异步控制器。我想向控制器发出REST请求,以便控制器立即返回,同时在服务器上继续工作。

我正在关注这个Spring示例:http://spring.io/blog/2012/05/07/spring-mvc-3-2-preview-introducing-servlet-3-async-support

我怀疑这是一个配置问题。有人可以告诉我我错过了什么吗?我是Spring的新手,所以如果你能提供尽可能多的详细信息,我们将不胜感激。

使用工作控制器我做了以下更改:

// Before
@RequestMapping(method=RequestMethod.POST)
public String processUpload(final MultipartFile file) {
    // ...
    return "someView";
}

// After
@RequestMapping(method=RequestMethod.POST)
public Callable<String> processUpload(final MultipartFile file) {

  return new Callable<String>() {
    public Object call() throws Exception {
      // ...
      return "someView";
    }
  };
}

我可以调用新控制器,但我有以下两个问题:

  1. 不会异步调用新控制器。浏览器中的浏览器挂起。该调用确实执行了代码。
  2. 请求因超出此错误而超时:

    2015-03-06 16:36:10.592 ERROR 13012 --- [MvcAsync1] o.s.w.c.request.async.WebAsyncManager:由于超时或网络错误导致无法完成异步处理

  3. 更新 我能够通过在我的应用程序文件中创建以下bean来解决超时问题:

    @Bean
    public EmbeddedServletContainerFactory servletContainer() {
            TomcatEmbeddedServletContainerFactory factory = new TomcatEmbeddedServletContainerFactory();
            factory.addConnectorCustomizers(new TomcatConnectorCustomizer() {
    
                @Override
                public void customize(Connector connector) {
    
                    connector.setPort(9000);
                    connector.setAsyncTimeout(60000);
                }
            });
            return factory;
        }
    

    但是对控制器的调用仍然不是异步的。浏览器在呼叫期间仍会挂起。

    我仍然在寻找有关如何在后台进行工作时立即对控制器进行REST调用的帮助。

    更新II

    谢谢戴夫。我试图在bean中实现异步方法。

    这是我的应用程序类:

    @SpringBootApplication
    @EnableAsync
    public class Application {
    
        public static void main(String[] args) {
            SpringApplication.run(Application.class, args);
        }
    
        @Bean
        public EmbeddedServletContainerFactory servletContainer() {
            TomcatEmbeddedServletContainerFactory factory = new TomcatEmbeddedServletContainerFactory();
            factory.addConnectorCustomizers(new TomcatConnectorCustomizer() {
    
                @Override
                public void customize(Connector connector) {
    
                    connector.setPort(9000);
                    connector.setAsyncTimeout(60000);
                }
            });
            return factory;
        }
     }
    

    这是我的bean类:

    public class LongProcess {
    
        @Async
        public Future<String> call() {
            try {
                System.out.println("Sleeping now...");
                Thread.sleep(10000);
                return new AsyncResult<String>("Hey");
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
                return null;
            }
        }
    
    }
    

    我的配置类:

    @Configuration
    @EnableAsync
    public class LongProcessConfiguration implements AsyncConfigurer {
    
        @Bean
        public LongProcess longProcessBean() {
            return new LongProcess();
        }
    
        @Override
        public Executor getAsyncExecutor() {
            ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
            taskExecutor.setMaxPoolSize(10);
            taskExecutor.setThreadNamePrefix("LULExecutor-");
            taskExecutor.initialize();
            return taskExecutor;
        }
    
        @Override
        public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
            return new SimpleAsyncUncaughtExceptionHandler();
        }
    
    }
    

    我的控制器方法:

    @RequestMapping("/utilities/longProcess")
        public String longProcess() {
    
            System.out.println("Starting long process...");
            CsvFileDifferConfiguration context = new CsvFileDifferConfiguration();
            LongProcess process = context.longProcessBean();
            Future<String> result = process.call();
            System.out.println("Done!");
            return "{success: 1}";
    
        }
    

    遗憾的是,这仍然不会立即返回。请注意,我不关心LongProcess的结果。该方法被成功调用,但不在后台调用。知道我可能会缺少什么吗?

    作为测试,如果我将控制器方法更改为等待结果,则永远不会输入等待块:

    @RequestMapping("/utilities/longProcess")
        public String longProcess() throws InterruptedException {
    
            System.out.println("Starting long process...");
            CsvFileDifferConfiguration context = new CsvFileDifferConfiguration();
            LongProcess process = context.longProcessBean();
            Future<String> result = process.call();
            while (!(result.isDone())) {
                Thread.sleep(1); //10-millisecond pause between each check
                System.out.println("Waiting for Long Process...");
            }
            System.out.println("Done!");
            return "{success: 1}";
    
        }
    

    更新III

    我替换了

    CsvFileDifferConfiguration context = new CsvFileDifferConfiguration();
                LongProcess process = context.longProcessBean();
    

    @Autowired
    private LongProcess process;
    

    这解决了这个问题。

1 个答案:

答案 0 :(得分:17)

我认为你误解了MVC异步(和Servlet 3)的功能。如果您的控制器方法需要很长时间才能完成,它将在与用于处理传入请求的线程不同的线程中调用,但它仍然必须在同一HTTP连接上将数据返回给客户端,因此它可能会超时那个观点。要立即返回但在后台进行处理,您不需要异步MVC,只需在后台线程中进行昂贵的处理(例如,在另一个@Async中调用@Bean方法)。