Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
1.0k views
in Technique[技术] by (71.8m points)

spring boot - How to implement an asynchronous REST request to a controller using Springboot?

I'm trying to implement an asynchronous controller using SprintBoot. I want to make REST request to a controller so that the controller returns immediately, while the work continues on the server.

I'm following this Spring example: http://spring.io/blog/2012/05/07/spring-mvc-3-2-preview-introducing-servlet-3-async-support

I suspect this is a configuration problem. Can someone please tell me what I'm missing? I'm new to Spring so if you could please provide as much details as possible it would be appreciated.

Using a working controller I made the following changes:

// 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";
    }
  };
}

I am able to call the new controller but I have two issues below:

  1. The new controller is not called asynchronously. The browser hangs during the call. The call does execute the code though.
  2. The request times out with this error:

    2015-03-06 16:36:10.592 ERROR 13012 --- [ MvcAsync1] o.s.w.c.request.async.WebAsyncManager : Could not complete async processing due to timeout or network error

Update: I was able to resolve the timeout by creating the following bean in my application file:

@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;
    }

But the call the to the controller is still not asynchronous. The browser still hangs for the duration of the call.

I'm still looking for help on how to make a the REST call to the controller return immediately while doing the work in the background.

Update II

Thank you Dave. I have attempted to implement an async method in a bean.

Here is my application class:

@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;
    }
 }

Here is my bean class:

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;
        }
    }

}

My configuration class:

@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();
    }

}

My controller method:

@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}";

    }

This unfortunately still does not return immediately. Note that I don't care about the result from LongProcess. The method is called successfully, but not in the background. Any idea what I might be missing?

As a test, if I change the controller method to wait for the result, the wait block is never entered:

@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}";

    }

Update III

I replaced

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

with

@Autowired
private LongProcess process;

and this solved the issue.

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

I think you misunderstand the MVC async (and Servlet 3) features. If your controller method takes a long time to complete it will be called in a different thread than the one used to handle the incoming request, but it still has to return data to the client on the same HTTP connection, so it can time out from that point of view. To return immediately but do the processing in the background you don't need async MVC, you just need to do the expensive processing in a background thread (e.g. by calling an @Async method in another @Bean).


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...